#include "gtkatspicontextprivate.h"
+#include "gtkatspirootprivate.h"
+#include "gtkdebug.h"
+#include "gtkwindow.h"
+
+#include <gio/gio.h>
+
#if defined(GDK_WINDOWING_WAYLAND)
# include <gdk/wayland/gdkwaylanddisplay.h>
#endif
#if defined(GDK_WINDOWING_X11)
# include <gdk/x11/gdkx11display.h>
+# include <gdk/x11/gdkx11property.h>
#endif
struct _GtkAtSpiContext
{
GtkATContext parent_instance;
+
+ /* The root object, used as a entry point */
+ GtkAtSpiRoot *root;
+
+ /* The address for the ATSPI accessibility bus */
+ char *bus_address;
+
+ /* The object path of the ATContext on the bus */
+ char *context_path;
+
+ /* Just a pointer; the connection is owned by the GtkAtSpiRoot
+ * associated to the GtkATContext
+ */
+ GDBusConnection *connection;
+};
+
+enum
+{
+ PROP_BUS_ADDRESS = 1,
+
+ N_PROPS
};
+static GParamSpec *obj_props[N_PROPS];
+
G_DEFINE_TYPE (GtkAtSpiContext, gtk_at_spi_context, GTK_TYPE_AT_CONTEXT)
static void
{
}
+static void
+gtk_at_spi_context_finalize (GObject *gobject)
+{
+ GtkAtSpiContext *self = GTK_AT_SPI_CONTEXT (gobject);
+
+ g_free (self->bus_address);
+ g_free (self->context_path);
+
+ G_OBJECT_CLASS (gtk_at_spi_context_parent_class)->finalize (gobject);
+}
+
+static void
+gtk_at_spi_context_set_property (GObject *gobject,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GtkAtSpiContext *self = GTK_AT_SPI_CONTEXT (gobject);
+
+ switch (prop_id)
+ {
+ case PROP_BUS_ADDRESS:
+ self->bus_address = g_value_dup_string (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ }
+}
+
+static void
+gtk_at_spi_context_get_property (GObject *gobject,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GtkAtSpiContext *self = GTK_AT_SPI_CONTEXT (gobject);
+
+ switch (prop_id)
+ {
+ case PROP_BUS_ADDRESS:
+ g_value_set_string (value, self->bus_address);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ }
+}
+
static void
gtk_at_spi_context_constructed (GObject *gobject)
{
+ GtkAtSpiContext *self = GTK_AT_SPI_CONTEXT (gobject);
+ GdkDisplay *display;
+
+ g_assert (self->bus_address);
+
+ /* Every GTK application has a single root AT-SPI object, which
+ * handles all the global state, including the cache of accessible
+ * objects. We use the GdkDisplay to store it, so it's guaranteed
+ * to be unique per-display connection
+ */
+ display = gtk_at_context_get_display (GTK_AT_CONTEXT (self));
+ self->root =
+ g_object_get_data (G_OBJECT (display), "-gtk-atspi-root");
+
+ if (self->root == NULL)
+ {
+ self->root = gtk_at_spi_root_new (self->bus_address);
+ self->connection = gtk_at_spi_root_get_connection (self->root);
+
+ g_object_set_data_full (G_OBJECT (display), "-gtk-atspi-root",
+ self->root,
+ g_object_unref);
+ }
+
+ /* We use the application's object path to build the path of each
+ * accessible object exposed on the accessibility bus; the path is
+ * also used to access the object cache
+ */
+ GApplication *application = g_application_get_default ();
+ char *base_path = NULL;
+
+ if (application != NULL)
+ {
+ const char *app_path = g_application_get_dbus_object_path (application);
+ base_path = g_strconcat (app_path, "/a11y", NULL);
+ }
+ else
+ {
+ char *uuid = g_uuid_string_random ();
+ base_path = g_strconcat ("/org/gtk/application/", uuid, "/a11y", NULL);
+ g_free (uuid);
+ }
+
+ /* We use a unique id to ensure that we don't have conflicting
+ * objects on the bus
+ */
+ char *uuid = g_uuid_string_random ();
+
+ self->context_path = g_strconcat (base_path, "/", uuid, NULL);
+
+ GTK_NOTE (A11Y, g_message ("ATSPI context path: %s", self->context_path));
+
+ g_free (base_path);
+ g_free (uuid);
+
G_OBJECT_CLASS (gtk_at_spi_context_parent_class)->constructed (gobject);
}
GtkATContextClass *context_class = GTK_AT_CONTEXT_CLASS (klass);
gobject_class->constructed = gtk_at_spi_context_constructed;
+ gobject_class->set_property = gtk_at_spi_context_set_property;
+ gobject_class->get_property = gtk_at_spi_context_get_property;
+ gobject_class->finalize = gtk_at_spi_context_finalize;
context_class->state_change = gtk_at_spi_context_state_change;
+
+ obj_props[PROP_BUS_ADDRESS] =
+ g_param_spec_string ("bus-address", NULL, NULL,
+ NULL,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (gobject_class, N_PROPS, obj_props);
}
static void
{
}
+#ifdef GDK_WINDOWING_X11
+static char *
+get_bus_address_x11 (GdkDisplay *display)
+{
+ GTK_NOTE (A11Y, g_message ("Acquiring a11y bus via X11..."));
+
+ Display *xdisplay = gdk_x11_display_get_xdisplay (display);
+ Atom type_return;
+ int format_return;
+ gulong nitems_return;
+ gulong bytes_after_return;
+ guchar *data = NULL;
+ char *address = NULL;
+
+ gdk_x11_display_error_trap_push (display);
+ XGetWindowProperty (xdisplay, DefaultRootWindow (xdisplay),
+ gdk_x11_get_xatom_by_name_for_display (display, "AT_SPI_BUS"),
+ 0L, BUFSIZ, False,
+ (Atom) 31,
+ &type_return, &format_return, &nitems_return,
+ &bytes_after_return, &data);
+ gdk_x11_display_error_trap_pop_ignored (display);
+
+ address = g_strdup ((char *) data);
+
+ XFree (data);
+
+ return address;
+}
+#endif
+
+#if defined(GDK_WINDOWING_WAYLAND) || defined(GDK_WINDOWING_X11)
+static char *
+get_bus_address_dbus (GdkDisplay *display)
+{
+ GTK_NOTE (A11Y, g_message ("Acquiring a11y bus via DBus..."));
+
+ GError *error = NULL;
+ GDBusConnection *connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
+
+ if (error != NULL)
+ {
+ g_critical ("Unable to acquire session bus: %s", error->message);
+ g_error_free (error);
+ return NULL;
+ }
+
+ GVariant *res =
+ g_dbus_connection_call_sync (connection, "org.a11y.Bus",
+ "/org/a11y/bus",
+ "org.a11y.Bus",
+ "GetAddress",
+ NULL, NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ &error);
+ if (error != NULL)
+ {
+ g_critical ("Unable to acquire the address of the accessibility bus: %s",
+ error->message);
+ g_error_free (error);
+ }
+
+ char *address = NULL;
+ if (res != NULL)
+ {
+ g_variant_get (res, "(s)", &address);
+ g_variant_unref (res);
+ }
+
+ g_object_unref (connection);
+
+ return address;
+}
+#endif
+
+static const char *
+get_bus_address (GdkDisplay *display)
+{
+ const char *bus_address;
+
+ bus_address = g_object_get_data (G_OBJECT (display), "-gtk-atspi-bus-address");
+ if (bus_address != NULL)
+ return bus_address;
+
+ /* The bus address environment variable takes precedence; this is the
+ * mechanism used by Flatpak to handle the accessibility bus portal
+ * between the sandbox and the outside world
+ */
+ bus_address = g_getenv ("AT_SPI_BUS_ADDRESS");
+ if (bus_address != NULL && *bus_address != '\0')
+ {
+ GTK_NOTE (A11Y, g_message ("Using ATSPI bus address from environment"));
+ g_object_set_data_full (G_OBJECT (display), "-gtk-atspi-bus-address",
+ g_strdup (bus_address),
+ g_free);
+ goto out;
+ }
+
+#if defined(GDK_WINDOWING_WAYLAND)
+ if (bus_address == NULL)
+ {
+ if (GDK_IS_WAYLAND_DISPLAY (display))
+ {
+ char *addr = get_bus_address_dbus (display);
+
+ g_object_set_data_full (G_OBJECT (display), "-gtk-atspi-bus-address",
+ addr,
+ g_free);
+
+ bus_address = addr;
+ }
+ }
+#endif
+#if defined(GDK_WINDOWING_X11)
+ if (bus_address == NULL)
+ {
+ if (GDK_IS_X11_DISPLAY (display))
+ {
+ char *addr = get_bus_address_dbus (display);
+
+ if (addr == NULL)
+ addr = get_bus_address_x11 (display);
+
+ g_object_set_data_full (G_OBJECT (display), "-gtk-atspi-bus-address",
+ addr,
+ g_free);
+
+ bus_address = addr;
+ }
+ }
+#endif
+
+out:
+ return bus_address;
+}
+
GtkATContext *
gtk_at_spi_create_context (GtkAccessibleRole accessible_role,
GtkAccessible *accessible,
g_return_val_if_fail (GTK_IS_ACCESSIBLE (accessible), NULL);
g_return_val_if_fail (GDK_IS_DISPLAY (display), NULL);
+ const char *bus_address = get_bus_address (display);
+
+ if (bus_address == NULL)
+ return NULL;
+
#if defined(GDK_WINDOWING_WAYLAND)
if (GDK_IS_WAYLAND_DISPLAY (display))
return g_object_new (GTK_TYPE_AT_SPI_CONTEXT,
"accessible-role", accessible_role,
"accessible", accessible,
"display", display,
+ "bus-address", bus_address,
NULL);
#endif
#if defined(GDK_WINDOWING_X11)
"accessible-role", accessible_role,
"accessible", accessible,
"display", display,
+ "bus-address", bus_address,
NULL);
#endif
--- /dev/null
+/* gtkatspiroot.c: AT-SPI root object
+ *
+ * Copyright 2020 GNOME Foundation
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "gtkatspirootprivate.h"
+
+#include "gtkdebug.h"
+#include "gtkwindow.h"
+
+#include "a11y/atspi/atspi-accessible.h"
+#include "a11y/atspi/atspi-application.h"
+
+#include <locale.h>
+
+#include <gio/gio.h>
+
+#define ATSPI_VERSION "2.1"
+#define ATSPI_ROOT_PATH "/org/a11y/atspi/accessible/root"
+
+struct _GtkAtSpiRoot
+{
+ GObject parent_instance;
+
+ char *bus_address;
+ GDBusConnection *connection;
+
+ const char *root_path;
+
+ const char *toolkit_name;
+ const char *version;
+ const char *atspi_version;
+
+ char *desktop_name;
+ char *desktop_path;
+
+ gint32 application_id;
+};
+
+enum
+{
+ PROP_BUS_ADDRESS = 1,
+
+ N_PROPS
+};
+
+static GParamSpec *obj_props[N_PROPS];
+
+G_DEFINE_TYPE (GtkAtSpiRoot, gtk_at_spi_root, G_TYPE_OBJECT)
+
+static void
+gtk_at_spi_root_finalize (GObject *gobject)
+{
+ GtkAtSpiRoot *self = GTK_AT_SPI_ROOT (gobject);
+
+ g_free (self->bus_address);
+ g_free (self->desktop_name);
+ g_free (self->desktop_path);
+
+ G_OBJECT_CLASS (gtk_at_spi_root_parent_class)->dispose (gobject);
+}
+
+static void
+gtk_at_spi_root_dispose (GObject *gobject)
+{
+ GtkAtSpiRoot *self = GTK_AT_SPI_ROOT (gobject);
+
+ g_clear_object (&self->connection);
+
+ G_OBJECT_CLASS (gtk_at_spi_root_parent_class)->dispose (gobject);
+}
+
+static void
+gtk_at_spi_root_set_property (GObject *gobject,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GtkAtSpiRoot *self = GTK_AT_SPI_ROOT (gobject);
+
+ switch (prop_id)
+ {
+ case PROP_BUS_ADDRESS:
+ self->bus_address = g_value_dup_string (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ }
+}
+
+static void
+gtk_at_spi_root_get_property (GObject *gobject,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GtkAtSpiRoot *self = GTK_AT_SPI_ROOT (gobject);
+
+ switch (prop_id)
+ {
+ case PROP_BUS_ADDRESS:
+ g_value_set_string (value, self->bus_address);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ }
+}
+
+
+static void
+handle_application_method (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *method_name,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation,
+ gpointer user_data)
+{
+}
+
+static GVariant *
+handle_application_get_property (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *property_name,
+ GError **error,
+ gpointer user_data)
+{
+ GtkAtSpiRoot *self = user_data;
+ GVariant *res = NULL;
+
+ if (g_strcmp0 (property_name, "Id") == 0)
+ res = g_variant_new_int32 (self->application_id);
+ else if (g_strcmp0 (property_name, "ToolkitName") == 0)
+ res = g_variant_new_string (self->toolkit_name);
+ else if (g_strcmp0 (property_name, "Version") == 0)
+ res = g_variant_new_string (self->version);
+ else if (g_strcmp0 (property_name, "AtspiVersion") == 0)
+ res = g_variant_new_string (self->atspi_version);
+ else
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+ "Unknown property '%s'", property_name);
+
+ return res;
+}
+
+static gboolean
+handle_application_set_property (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *property_name,
+ GVariant *value,
+ GError **error,
+ gpointer user_data)
+{
+ GtkAtSpiRoot *self = user_data;
+
+ if (g_strcmp0 (property_name, "Id") == 0)
+ {
+ g_variant_get (value, "i", &(self->application_id));
+ }
+ else
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+ "Invalid property '%s'", property_name);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+handle_accessible_method (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *method_name,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation,
+ gpointer user_data)
+{
+ g_printerr ("[Accessible] Method '%s' on interface '%s' for object '%s' from '%s'\n",
+ method_name, interface_name, object_path, sender);
+
+}
+
+static GVariant *
+handle_accessible_get_property (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *property_name,
+ GError **error,
+ gpointer user_data)
+{
+ GtkAtSpiRoot *self = user_data;
+ GVariant *res = NULL;
+
+ if (g_strcmp0 (property_name, "Name") == 0)
+ res = g_variant_new_string (g_get_prgname ());
+ else if (g_strcmp0 (property_name, "Description") == 0)
+ res = g_variant_new_string (g_get_application_name ());
+ else if (g_strcmp0 (property_name, "Locale") == 0)
+ res = g_variant_new_string (setlocale (LC_MESSAGES, NULL));
+ else if (g_strcmp0 (property_name, "AccessibleId") == 0)
+ res = g_variant_new_string ("");
+ else if (g_strcmp0 (property_name, "Parent") == 0)
+ res = g_variant_new ("(so)", self->desktop_name, self->desktop_path);
+ else if (g_strcmp0 (property_name, "ChildCount") == 0)
+ {
+ int n_children = 0;
+
+ if (g_strcmp0 (object_path, ATSPI_ROOT_PATH) == 0)
+ {
+ GList *windows = gtk_window_list_toplevels ();
+
+ /* We are only interested in the visible top levels */
+ for (GList *l = windows; l != NULL; l = l->next)
+ {
+ if (gtk_widget_is_visible (l->data))
+ n_children += 1;
+ }
+
+ g_list_free (windows);
+ }
+
+ res = g_variant_new_int32 (n_children);
+ }
+ else
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+ "Unknown property '%s'", property_name);
+
+ return res;
+}
+
+static const GDBusInterfaceVTable root_application_vtable = {
+ handle_application_method,
+ handle_application_get_property,
+ handle_application_set_property,
+};
+
+static const GDBusInterfaceVTable root_accessible_vtable = {
+ handle_accessible_method,
+ handle_accessible_get_property,
+ NULL,
+};
+
+static void
+on_registration_reply (GObject *gobject,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GtkAtSpiRoot *self = user_data;
+
+ GError *error = NULL;
+ GVariant *reply = g_dbus_connection_call_finish (G_DBUS_CONNECTION (gobject), result, &error);
+
+ if (error != NULL)
+ {
+ g_critical ("Unable to register the application: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+
+ if (reply != NULL)
+ {
+ g_variant_get (reply, "((so))",
+ &self->desktop_name,
+ &self->desktop_path);
+ g_variant_unref (reply);
+
+ GTK_NOTE (A11Y, g_message ("Connected to the a11y registry at (%s, %s)",
+ self->desktop_name,
+ self->desktop_path));
+ }
+}
+
+static void
+gtk_at_spi_root_register (GtkAtSpiRoot *self)
+{
+ /* Register the root element; every application has a single root, so we only
+ * need to do this once.
+ *
+ * The root element is used to advertise our existence on the accessibility
+ * bus, and it's the entry point to the accessible objects tree.
+ *
+ * The announcement is split into two phases:
+ *
+ * 1. we register the org.a11y.atspi.Application and org.a11y.atspi.Accessible
+ * interfaces at the well-known object path
+ * 2. we invoke the org.a11y.atspi.Socket.Embed method with the connection's
+ * unique name and the object path
+ * 3. the ATSPI registry daemon will set the org.a11y.atspi.Application.Id
+ * property on the given object path
+ * 4. the registration concludes when the Embed method returns us the desktop
+ * name and object path
+ */
+ self->toolkit_name = "GTK";
+ self->version = PACKAGE_VERSION;
+ self->atspi_version = ATSPI_VERSION;
+ self->root_path = ATSPI_ROOT_PATH;
+
+ g_dbus_connection_register_object (self->connection,
+ self->root_path,
+ atspi_application_interface_info (),
+ &root_application_vtable,
+ self,
+ NULL,
+ NULL);
+ g_dbus_connection_register_object (self->connection,
+ self->root_path,
+ atspi_accessible_interface_info (),
+ &root_accessible_vtable,
+ self,
+ NULL,
+ NULL);
+
+ GTK_NOTE (A11Y, g_message ("Registering (%s, %s) on the a11y bus",
+ g_dbus_connection_get_unique_name (self->connection),
+ self->root_path));
+
+ g_dbus_connection_call (self->connection,
+ "org.a11y.atspi.Registry",
+ ATSPI_ROOT_PATH,
+ "org.a11y.atspi.Socket",
+ "Embed",
+ g_variant_new ("((so))",
+ g_dbus_connection_get_unique_name (self->connection),
+ self->root_path
+ ),
+ G_VARIANT_TYPE ("((so))"),
+ G_DBUS_CALL_FLAGS_NONE, -1,
+ NULL,
+ on_registration_reply,
+ self);
+}
+
+static void
+gtk_at_spi_root_constructed (GObject *gobject)
+{
+ GtkAtSpiRoot *self = GTK_AT_SPI_ROOT (gobject);
+
+ GError *error = NULL;
+
+ /* The accessibility bus is a fully managed bus */
+ self->connection =
+ g_dbus_connection_new_for_address_sync (self->bus_address,
+ G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT |
+ G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION,
+ NULL, NULL,
+ &error);
+
+ if (error != NULL)
+ {
+ g_critical ("Unable to connect to the accessibility bus at '%s': %s",
+ self->bus_address,
+ error->message);
+ g_error_free (error);
+ goto out;
+ }
+
+ gtk_at_spi_root_register (self);
+
+out:
+ G_OBJECT_CLASS (gtk_at_spi_root_parent_class)->constructed (gobject);
+}
+
+static void
+gtk_at_spi_root_class_init (GtkAtSpiRootClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->constructed = gtk_at_spi_root_constructed;
+ gobject_class->set_property = gtk_at_spi_root_set_property;
+ gobject_class->get_property = gtk_at_spi_root_get_property;
+ gobject_class->dispose = gtk_at_spi_root_dispose;
+ gobject_class->finalize = gtk_at_spi_root_finalize;
+
+ obj_props[PROP_BUS_ADDRESS] =
+ g_param_spec_string ("bus-address", NULL, NULL,
+ NULL,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (gobject_class, N_PROPS, obj_props);
+}
+
+static void
+gtk_at_spi_root_init (GtkAtSpiRoot *self)
+{
+}
+
+GtkAtSpiRoot *
+gtk_at_spi_root_new (const char *bus_address)
+{
+ g_return_val_if_fail (bus_address != NULL, NULL);
+
+ return g_object_new (GTK_TYPE_AT_SPI_ROOT,
+ "bus-address", bus_address,
+ NULL);
+}
+
+GDBusConnection *
+gtk_at_spi_root_get_connection (GtkAtSpiRoot *self)
+{
+ g_return_val_if_fail (GTK_IS_AT_SPI_ROOT (self), NULL);
+
+ return self->connection;
+}